package org.fjsei.yewu.bpm;

import io.camunda.tasklist.CamundaTaskListClient;
import io.camunda.tasklist.CamundaTasklistClientConfiguration;
import io.camunda.tasklist.auth.Authentication;
import io.camunda.tasklist.auth.SimpleAuthentication;
import io.camunda.tasklist.auth.SimpleCredential;
import io.camunda.tasklist.dto.Pagination;
import io.camunda.tasklist.dto.Task;
import io.camunda.tasklist.dto.TaskList;
import io.camunda.tasklist.dto.TaskState;
import io.camunda.tasklist.exception.TaskListException;
import io.camunda.zeebe.client.ZeebeClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.time.Duration;

/**【官方】  https://github.com/camunda-community-hub/camunda-tasklist-client-java  不能少。
 *版本不匹配导致following method did not exist:java.util.Map$Entry io.camunda.common.auth.Authentication.getTokenHeader 源码看api "io.camunda.spring:java-common:8.5.3"  api "io.camunda:zeebe-client-java:8.5.3"
 * 引进单独的流程引擎服务目的是：免去关系数据库的压力，提高性能。 Usertask竟然没有超时的概念？jobDeadline；@ BPMN Timer boundary events interrupting;
 * zeebe自己不处理业务逻辑，也没有流程节点的超时处理机制，用户任务超时的定义实际要在BPMN流程图上定义和处理的，流程节点内容zeebe底层才不关心的，要上层处理。
 * 直接利用现成的camunda-tasklist-8.1.0工具模块作为独立的服务器，然后我这再通过camunda-tasklist-client-java提供的graphQL-API接口apolloClient访问用户流程任务列表。
 * 问题：camunda-tasklist是个独立web服务器？单点故障，需要2到3台机器上部署成集群的来实现高可用性？camunda-tasklist的直接存储库ES在多个web服务器会不会并发冲突啊？
 * 这个包io.generated.tasklist.client实际上是apollographql.apollo3工具自动产生的。
 * Tasklist服务器也不过是个数据二传手的，启动该服务目的是搜索某个用户和某个用户组的当前UserTask列表的。
 * 流程引擎服务Zeebe：对比上一代Camunda版本区别：旧版需要在RMDB数据库存49个表的进程内独立模块服务形式，新的Zeebe自带集群KV存储另外自动导出数据给ElasticSearch存储以便于用户任务查询和流程运行跟踪+异常分析。
 流程引擎集群对外用gRPC协议的， API包装 不止一层  https://docs.camunda.io/docs/apis-clients/java-client/
 zeebe的客户机｛包含应用后端以及tasklist和operate这两个网站的服务器在这其实也是看做zeebe客户机｝是使用gRPC协议连接流程引擎集群的。
 实际流程客户后端服务器：应该用spring-zeebe-starter来间接使用zeebe-client-java而不是直接用这个包的。
 implementation group: 'io.camunda', name: 'spring-zeebe-starter', version: '8.0.5'已经改成 implementation 'io.camunda.spring:spring-boot-starter-camunda:8.5.8'
 流程的UserTask应该是从Tasklist服务器中转的 ，需要套上一个工具包：
 implementation 'io.camunda:camunda-tasklist-client-java:1.0.2'
 流程实例故障Incident跟踪以及处理的需要，就得要Operate服务器支持，虽然Operate和Tasklist两个服务端实际没有依赖关系的 ，需要套上一个工具包：
 implementation 'io.camunda:camunda-operate-client-java:1.0.0'
 流程的启动实例生成实际上只需要spring-zeebe-starter这个包就可以了。但是流程故障有问题的只能绕道Operate服务器，该服务端启动了才能查得出来。
 Zeebe Broker流程引擎集群上面自带ES Exporter的，就算Operate服务和Tasklist服务全都宕机的，ElasticSearch集群存储上也应该能够收到并存储新生成流程数据的zeebe-record*开头的索引。
 说白了：Operate和Tasklist这两个以及配套的tasklist-client和operate-client,其实就是从ES存储索引当中重新挖掘整理流程数据的，又倒腾整理一遍流程状态数据。不用这两个工具只能自己去zeebe-record*索引查找了。
 有三份类别的流程实例数据：第一是zeebe集群K/V存储的；第二是ES集群上zeebe-record*开头的索引们；第三ES集群上tasklist-*以及operate-*开头的索引们,而且ES索引数量众多，过期就需要清理。
 另外spring-zeebe-starter也只能提供直联流程引擎功能；并没有涉及可以放弃使用Tasklist或Operate可替代掉的功能啊。目前zeebe-client tasklist operate这3种API不能相互替代。
 operate服务留它何用 ?：流程故障查询显示，前端管理员登录要维护的流程故障列表的支持。
 tasklist服务留它何用 ?：应用后端需要给前端的提供列表UserTask队列+过滤分页。
 集成的-client*-包的升级：要求tasklist / operate服务器需重启,这俩个服务端还牵涉到zeebe版本升级的遗留数据迁移的配套功能。
 生产系統ES庫的專用性，zeebe应獨立部署的ES庫+用戶權限，不应该和其它ES索引共享ES服务器，索引数量太多。
 【异常】若ES分片数量超1000不足的，zeebe:broker-service异常的，有可能流转看似成功实际上却没有起作用，这段时间不能反馈给前端，但是流转操作依然执行。
 broker内核存储以及Es_Expoter输出zeebe-record_索引并不是全量流程实例，这两个实际是事件流。tasklist-索引，operate-索引实际是从前面Expoter输出索引读取的，但是这两个反而不能随意删除啊。
 operate-和tasklist-两类索引实际上完整存储了历史流程实例，万一把有用的未完成实例直接删除，那么可能就无法继续流转了，丢失！反而Expoter输出zeebe-record_索引允许武断地删除。broker内核存储/data目录不能删除。
 清理zeebe旧数据正确顺序：zeebe-record_索引首先清理掉，然后才是清理后面Operate- Tasklist-两组索引（页面或接口方式）！自动恢复问题。
 Form概念：Tasklist imports this form schema 显示输入输出对话框的，不是必须的 form ! 是UI层面配套的数据存储。而 form 必须附着在tasklist客户端上的。
 * */

//@ConditionalOnBean(CamundaClientProperties.class)
//@EnableConfigurationProperties({CamundaClientProperties.class})
@Service
public class UserTaskListService {
    //借用配置： ？都是一个账户密码的；
//    private final CamundaClientProperties properties;
    Authentication auth;
    CamundaTaskListClient  client;
    //改为 CamundaClient
    @Autowired
    ZeebeClient zeebeClient;
    //简单独立部署环境用的：To use if Tasklist is not configured with Identity and Keycloak情况用的。
    public UserTaskListService() throws TaskListException {

//        String baseUrl=this.properties.getTasklist().getBaseUrl().toString();
//        //这个用户口令是针对 tasklist服务端的，并不是zeebe服务器的。 非SaaS, 也没用 Identity & Keycloak。
////        sa = new SimpleAuthentication("herzhang", "testuko");
//        SimpleConfig simpleConf = new SimpleConfig();
//        simpleConf.addProduct(Product.TASKLIST, new SimpleCredential(baseUrl,
//                this.properties.getAuth().getUsername(), this.properties.getAuth().getPassword()));
//        auth = SimpleAuthentication.builder().withSimpleConfig(simpleConf).build();
    }

    @PostConstruct       //启动时间重建索引啊，耗时间： #问题启动失败，无法继续走。
    public void initialize() throws TaskListException {

        //camunda-zeebe和-operate和-tasklist这三个服务端实例集群化。zeebe有问题后端启动失败。operate和tasklist没连上不影响后端启动。
        //若IP地址切换的，要求zeebe服务的 ./data/目录清空旧数据啊，否则无法正常启动。
//        String baseUrl=this.properties.getTasklist().getBaseUrl().toString();
//        if(!this.properties.getTasklist().getEnabled())  return;
        try {
//            client = new CamundaTaskListClient.Builder().taskListUrl("http://localhost:8281").shouldReturnVariables().authentication(sa).build();
            //io.camunda.common.auth.Authentication.getTokenHeader(io.camunda.common.auth.Product)'
            //报错： multiple 'Set-Cookie' headers found
//            client = CamundaTaskListClient.builder()
//                    .taskListUrl(baseUrl)
//                    .shouldReturnVariables().shouldLoadTruncatedVariables()
//                    .authentication(auth)
////                    .cookieExpiration(Duration.ofSeconds(5))
//                    .build();

            // properties you need to provide
            String username = "demo";
            String password = "demo";
            URL tasklistUrl = URI.create("http://localhost:8082").toURL();
            boolean returnVariables = false;
            boolean loadTruncatedVariables = false;
            boolean useZeebeUserTasks = true;
// if you are using zeebe user tasks, you require a zeebe client as well
//            ZeebeClient zeebeClient = zeebeClient();        //只有 CamundaClient
//            CamundaClient
// bootstrapping
            SimpleCredential credentials =
                    new SimpleCredential(username, password, tasklistUrl, Duration.ofMinutes(10));
            SimpleAuthentication authentication = new SimpleAuthentication(credentials);
            CamundaTasklistClientConfiguration configuration =
                    new CamundaTasklistClientConfiguration(
                            authentication,
                            tasklistUrl,
                            zeebeClient,
                            new CamundaTasklistClientConfiguration.DefaultProperties(returnVariables, loadTruncatedVariables, useZeebeUserTasks));
            client = new CamundaTaskListClient(configuration);

            //登录/api/login成功返回HttpHeader(name=Cookie, value=TASKLIST-SESSION=3A725C935E84408784F0B5D1AFB4E804; Path=/; HttpOnly; SameSite=Lax)随后每个请求带上Cookie
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
        //实际上封装了apollo Client方式访问对方的服务器graphQL API。完全可自己定义代码的：spring for Graphql的Client编码能力。
        //List<Task> tasks = client.getTasks(null, null, TaskState.CREATED, 20);   //参数TaskStatech不能省略的
        //assignee 实际和List<String> candidateGroups 都是自定义Bpmn:CustomHeader{}里面的。和zeebe引擎没关系，上层应用自己管理的。
        //claim声明我来处理：unclaim(),用户组要自己管理？ UserTask完成提交client.completeTask(task.getId(), Map.of("toto", "x")); 设置Assignee;
    }

    /**实际上又套了一层：我这通过camunda-tasklist-client-java包来对接Tasklist服务器提供的graphQL接口能力，最后Tasklist的存储实际挖掘来自ES索引存储zeebe_record，
     * 而zeebe_record索引是由流程引擎服zeebe主动导出的, zeebe/Broker.bat它实际内部也有自己管理的最原始存储的。倒腾2遍的流程数据啊。
     * 指定用户uid,指定用户组gids，指定任务过滤参数，指定任务列表排序顺序，指定一次性读出最多或许大约数量。 用户组的组别数量可能很多个是个问题，又无法预计合并的队列预计任务数量？
     * 根据gids的每一个用户组gid都去搜索，slopSize是每一个搜索分页大小，pageSize是最大预定返回任务数，实际数也能超出的。
     * 合并之后排序受到影响：还是让前端直接决定要不要汇总后总的重做排序。
     * 设定前提#：前端应该处理UserTask,而不是挑剔的，再做过滤，否则队列如何更新，如何翻页查看下一页列表啊？camunda-tasklist-client-java缺陷，考虑自己直接访问ES存储索引?
     * camunda-tasklist-client-java自身也是graphQL客户端的方式再去访问的Tasklist服务器的。
     * 其实Tasklist服务端的graphQL接口上面就有提供的searchAfter[]滚动分页,只是客户端apollo自己限制死了。
     * 过滤功能Tasklist服务graphQL也只有设计了TaskState  assigned  assignee  candidateGroup  taskDefinitionId几个，最有意义taskDefinitionId=流程的节点任务标记。
     * 实际assignee用户和组candidateGroups[]这两个特别和zeebe的Broker没半点关系，在Bpmn文件定义的，获取值变量Variables,客户端层次的应用自己管理的。
     * 对方使用apollographql的分页。Pagination 透传？：如何跨请求包组织 Pagination {pageSize; List<String> search;SearchType searchType;｝参数=二次打包。
     * 另外groupIds也不至仅仅一个用户组。多个用户组只能执行多个查询后合并吗,用户主动获取某一个用户组驱动的{必查点了.PK.再查用户组},对法接口String group只能单一个名字不是多个组名都能匹配的。
     * 所以流程尽量不要用用户组分配的方式，避免3个和尚没水吃的尴尬，最好指定到个人，组用户尽量的少数几个人，人多了就没意义。
     * 【考虑】拆分= 个人:组任务(单个组名|null) 两个前端独立页面claim。
     * 如何区分哪些用户组组名在流程引擎当中用到了，还是没用过，没必要查用户组：特别标记？后台维护定时器任务(30分钟轮询一次)：单独查询用户组流程job同时设置用户组的流程关联Exists标记。
     * apollo排序+分页的接续定位：依据 $searchAfter: [String!], $searchBefore:$searchAfterOrEqual实际对应Pagination里面的search[String]队列：sortValues。
     * 用戶组不等于角色的概念。其实group可以改成在BPMN的子流程事件来处置多人有一个人处理该用户任务即可过关的，逻辑放入引擎；这里就可免于启用groupId用户组的概念。
     * 服务端ES原生可搜索过滤？可我这里通过camunda-tasklist-client-java/io/camunda/tasklist/CamundaTaskListClient接入却是无法对usertask进行过滤排序的。
     * */
    public TaskList fetchMyUserTask(String userId, Pagination page){
        try {
            if(null!=client) {
                //优先userId查找：专门分配到单个人的    ？时间顺序优先级排序  【匹配】Assignee= userId的zeebe用户任务。getTasks 改为 getTasksWithVariables
                //searchAfter 组装性能Pagination的search=[];  ？？AFTER_OR_EQUAL
                TaskList tasks = client.getAssigneeTasks( userId, TaskState.CREATED, page);
              //@还不支持candidateUsers @ TaskList tasks =client.getTasks(null,false,userId, TaskState.CREATED, true, page);
                //TaskList nextTasks= client.afterOrEqual(tasks); 提取最新的searchAfter= sortValues[]; AfterValues
                //int size = tasks.size();
                    //不指定具体组名null+还需分页复杂情况。分页的组名必须保持不变啊。？还能局限下一个组名的最大一个页的任务可读出来=无法分页loadNext()因为组名没特指的+组名也要加入到cursor编码。
                    //tasks = client.getGroupTasks(groupId,TaskState.CREATED,slopSize);        //已经有了Assignee的也能够返回的！
                    //可能需要client.claim(task.getId(), "herzhang");
                //[插入即刻过滤] 假设被服务端手动过滤之后的数量=0的&&TaskList显示可能还有的未被搜看光的可能的：必须重新查询！
                return tasks;
            }
        } catch (TaskListException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**用户交付任务：获取底层流程全局变量。
     * */
    public Task getTask(String userTaskId){
        try {
            if(null!=client) {
                Task task = client.getTask( userTaskId, true);
                return task;
            }
        } catch (TaskListException e) {
            e.printStackTrace();
        }
        return null;
    }
}


/*
注意：流程已经产生的，遗留的，BPMN变更不会对旧的流程实例有影响的。【所以要求zeebe】流程周期尽量短暂，太长必然有变：版本脱轨，预期混乱！
FEEL表达 https://docs.camunda.io/docs/next/components/modeler/feel/what-is-feel/
文档来自  https://docs.camunda.io/docs/components/modeler/feel/builtin-functions/feel-built-in-functions-list/
zeebe最新服务器 底层3组3集群部分： https://github.com/camunda/zeebe/releases
Zeebe 的发布订阅模式 Kafka:     https://www.finclip.com/news/f/3580.html
看资源Zeebe瓶颈主要在磁盘IO上。      https://blog.csdn.net/lingdu_789/article/details/125103315
可惜Tasklis还不能支持candidateUsers它：BPMN配置candidateUsers candidateGroups无法代替assignee:  必须经过前置的Claim环节: 用户任务列表需要区分UnCliam还得Claim操作。
ZeebeClientLifecycle:: newModifyProcessInstanceCommand(long processInstanceKey)直接操作某一个具体实例的。
Zeebe 介绍，去中心化，基于gRPC + HTTP/2 的传输；    https://xie.infoq.cn/article/4e361655b83634d2a3a0c9a61
分布式系统 Zeebe 和 有自带类似于Kafka功能，企业服务总线ESB类似Kafka；主要是微服务来的。      https://www.likecs.com/show-308050797.html
Zeebe不再使用关系数据库，RocksDB磁盘，Raft 共识，gRPC。          https://www.jdon.com/52803.html
ETL工具 ：StreamSets大数据实时采集；  HDFS存储, ES存储。          https://zhuanlan.zhihu.com/p/83314252
Apache Pulsar 客户端通过主题与消息系统进行交互；Caffeine Cache；大数据处理:Apache Storm集群{对比Hadoop}；      https://blog.csdn.net/wapecheng/article/details/125372772
*【清理】 旧的数据，ES分片爆满了1000个限制出错。删除旧的很多个索引。除了这底下6个，其它zeebe相关ES存储可全部删除。 生产系统如何，时间跨度，最老的业务流程？ 还没有执行完毕的流程呢。 ES分片(索引数量*N?3分片)；
这2个索引tasklist-process-$ver。operate-process-$ver不能删除！导致丢失定义中文名字，若删除就必须重新部署流程定义需要修改文件日期强制升级其版本号。
假如operate-process-$ver删除使得旧版本流程定义丢失，无法操作旧的未完成流程实例，连工具网站也无法提取旧的流程，只能对zeebe集群的底层自带K/V存储\camunda-zeebe-\data进行目录删除的？
删除未完成流程实例再找回【恢复难】，遗留的未完成流程实例都能找回来?。这时UserTask发现有变量却丢失无法流转啊,？Operate是空。
zeebe-record_*索引不能删除=像是数据源，删了如何从K/V恢复？。 operate-或tasklist-索引除了上面2个都可删除！Operate网站删除接口无法删除所有垃圾数据，zeebe-record_*索引无法靠它清理干净的。
Elasticsearch exporter导出器从不删除数据; 默认批量更新。“如果没有配置导出器，Zeebe会在不再需要时自动擦除数据。一旦Zeebe不再需要数据，它会询问其Exporter是否可以安全删除数据，如果可以，则会永久删除数据，从而减少磁盘使用量。”
zeebe-broker为何反而要依赖于导出的数据呢：反向依赖，zeebe-record_*索引反而成了全面存储的基准了，这样子broker内部的K/V自带文件工作性质内核存储反而可能不算是完整的数据了。zeebe-record_*索引反客为主。
-Optimize无法安装，报表的；多住户？Tenants。
D:\fbs\camunda-zeebe-8.1.6/Broker进程内置默认的io.camunda.zeebe.exporter.ElasticsearchExporter 的配置参数 ，允许可配置参数自动删除旧的event stream,安全删除历史流程实例。
自动清理旧的流程实例,真的是-Exporter输出的存储才算完备流程状态存储的，retention.enabled: minimumAge: 30d：    https://github.com/camunda/zeebe/tree/main/exporters/elasticsearch-exporter
ILM 竟然依赖ES的配置策略来自动删除的，-Exporter不是自己清理的，让ES代劳删除。问题啊：尽快移出有用数据？
内核broker:/data/raft-partition删除会导致'NOT_FOUND': Expected to complete job with key '', but no such job was found",而且在Operate Tasklist也都会无法清理掉，就麻烦了=
哪呀要手动做清理tasklist/operate索引，而且zeebe-record_job_8.1.6_$Date里面也有的也得清理JOB。
在zeebe-record_索引数据没有缺失情景下，若直接删除Operate Tasklist两组索引后重启，有可能自动恢复旧的流程实例。手动清理Operate Tasklist旧数据操作根本不会影响到zeebe-record_索引，又会自动会恢复啊，所以zeebe-record_肯定首先清理掉的。
tasklist-索引，operate-索引 以及*_Expoter输出zeebe-record_索引, 这三类后面名字加了日期的都是旧的数据索引，可以删除；zeebe-record_当日的不能随便删。
zeebe这三个大块的ES索引都类似处置的，索引没用的尾部加日期。
 */

